package com.fenxiangtech.mall.service.impl;

import com.fenxiangtech.mall.exception.OrderMissException;
import com.fenxiangtech.mall.exception.UnknownErrorException;
import com.fenxiangtech.mall.dto.OrderStatus;
import com.fenxiangtech.mall.dto.Result;
import com.fenxiangtech.mall.entity.Order;
import com.fenxiangtech.mall.enums.OrderStatusEnum;
import com.fenxiangtech.mall.exception.OrderPaidException;
import com.fenxiangtech.mall.exception.TokenInvalidException;
import com.fenxiangtech.mall.service.OrderService;
import com.fenxiangtech.mall.service.PayService;
import com.fenxiangtech.mall.service.UserTokenService;
import com.fenxiangtech.mall.wxpay.sdk.WXPay;
import com.fenxiangtech.mall.wxpay.sdk.WXPayConfigImpl;
import com.fenxiangtech.mall.wxpay.sdk.WXPayConstants;
import com.fenxiangtech.mall.wxpay.sdk.WXPayUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zhangjiashun
 */
@Service
public class PayServiceImpl implements PayService {

    private static final Logger log = LoggerFactory.getLogger(PayServiceImpl.class);

    @Autowired
    private OrderService orderService;

    @Autowired
    private UserTokenService userTokenService;

    @Value("wxAppID")
    private String wxAppID;

    @Override
    public Result pay(Long orderID) {
        Result result = new Result();
        this.checkOrderValid(orderID);
        OrderStatus orderStatus = orderService.checkOrderStock(orderID);
        if (!orderStatus.isSuccess()) {
            result.setSuccess(false);
            result.setData(orderStatus);
            return result;
        }
        Order order = orderService.getOrderByID(orderID);
        Map<String, String> wxOrder = this.makeWxPreOrder(order);
        this.recordPreOrder(wxOrder, orderID);
        Map<String, String> data = this.sign(wxOrder);
        result.setSuccess(true);
        result.setData(data);
        return result;
    }

    @Override
    @Transactional
    public boolean notifyProcess(Map<String, String> data) {
        if (data.get("result_code").equals("SUCCESS")) {
            try {
                String orderNo = data.get("out_trade_no");
                Order order = orderService.getOrderByOrderNo(orderNo);
                if (order.getStatus() == OrderStatusEnum.UNPAID.getStatus()) {
                    OrderStatus orderStatus = orderService.checkOrderStock(order.getId());
                    if (orderStatus.isSuccess()) {
                        orderService.updateOrderStatus(order.getId(), OrderStatusEnum.PAID);
                        orderService.reduceStock(orderStatus);
                    } else {
                        orderService.updateOrderStatus(order.getId(),
                                OrderStatusEnum.PAID_BUT_OUT_OF);
                    }
                }
                return true;
            } catch (Exception ex) {
                log.error(ex.getMessage(), ex);
                return false;
            }
        }
        return false;
    }

    private Map<String, String> sign(Map<String, String> wxOrder) {
        long timestamp = WXPayUtil.getCurrentTimestamp();
        String nonceStr = WXPayUtil.generateNonceStr();
        String packageStr = "prepay_id=" + wxOrder.get("prepay_id");
        String appID = this.wxAppID;
        Map<String, String> data = new HashMap<>();
        data.put("timeStamp", String.valueOf(timestamp));
        data.put("nonceStr", nonceStr);
        data.put("package", packageStr);
        String signature;
        try {
            signature = WXPayUtil.generateSignature(data, appID);
            data.put("paySign", signature);
            data.put("signType", WXPayConstants.MD5);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new UnknownErrorException();
        }
        return data;
    }

    private Map<String, String> makeWxPreOrder(Order order) {
        String openid = (String) userTokenService.getCurrentTokenVar("openid");
        if (openid == null || "".equals(openid)) {
            throw new TokenInvalidException();
        }
        WXPay wxPay;
        try {
            wxPay = new WXPay(WXPayConfigImpl.getInstance());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new UnknownErrorException();
        }
        Integer totalPrice = order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue();
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("body", "零食商贩");
        data.put("out_trade_no", order.getOrderNo());
        data.put("device_info", "");
        data.put("fee_type", "CNY");
        data.put("total_fee", totalPrice.toString());
        data.put("spbill_create_ip", "123.12.12.123");
        data.put("notify_url", "http://test.letiantian.me/wxpay/notify");
        data.put("trade_type", "NATIVE");
        data.put("product_id", "12");
        return this.getPaySignature(wxPay, data);
    }

    private boolean recordPreOrder(Map<String, String> wxOrder, Long orderID) {
        return orderService.recordPrepayID(wxOrder.get("prepay_id"), orderID);
    }

    private Map<String, String> getPaySignature(WXPay wxPay, Map<String, String> data) {
        try {
            Map<String, String> result = wxPay.unifiedOrder(data);
            return result;
        } catch (Exception e) {
            log.error("获取预支付订单失败, " + e.getMessage(), e);
            throw new UnknownErrorException();
        }
    }

    private boolean checkOrderValid(Long orderID) {
        boolean orderBelongCurrentUser = orderService.orderBelongCurrentUser(orderID);
        if (!orderBelongCurrentUser) {
            throw new OrderMissException("该订单不属于当前用户");
        }
        int status = orderService.getOrderStatus(orderID);
        if (status != OrderStatusEnum.UNPAID.getStatus()) {
            throw new OrderPaidException();
        }
        return true;
    }
}
